/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
/**
 * @author Alexey A. Petrenko
 */
package org.apache.harmony.awt.gl;

import java.text.AttributedCharacterIterator;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.harmony.awt.gl.font.FontManager;
import org.apache.harmony.awt.gl.font.FontMetricsImpl;
import org.apache.harmony.awt.gl.font.fontlib.FLTextRenderer;
import org.apache.harmony.awt.gl.image.OffscreenImage;
import org.apache.harmony.awt.gl.render.Blitter;
import org.apache.harmony.awt.gl.render.JavaArcRasterizer;
import org.apache.harmony.awt.gl.render.JavaLineRasterizer;
import org.apache.harmony.awt.gl.render.JavaShapeRasterizer;
import org.apache.harmony.awt.gl.render.JavaTextRenderer;
import org.apache.harmony.awt.gl.render.NullBlitter;

import com.jgraph.gaeawt.java.awt.AlphaComposite;
import com.jgraph.gaeawt.java.awt.BasicStroke;
import com.jgraph.gaeawt.java.awt.Color;
import com.jgraph.gaeawt.java.awt.Composite;
import com.jgraph.gaeawt.java.awt.Font;
import com.jgraph.gaeawt.java.awt.FontMetrics;
import com.jgraph.gaeawt.java.awt.Graphics2D;
import com.jgraph.gaeawt.java.awt.GraphicsConfiguration;
import com.jgraph.gaeawt.java.awt.Image;
import com.jgraph.gaeawt.java.awt.Paint;
import com.jgraph.gaeawt.java.awt.PaintContext;
import com.jgraph.gaeawt.java.awt.Point;
import com.jgraph.gaeawt.java.awt.Polygon;
import com.jgraph.gaeawt.java.awt.Rectangle;
import com.jgraph.gaeawt.java.awt.RenderingHints;
import com.jgraph.gaeawt.java.awt.Shape;
import com.jgraph.gaeawt.java.awt.Stroke;
import com.jgraph.gaeawt.java.awt.font.FontRenderContext;
import com.jgraph.gaeawt.java.awt.font.GlyphVector;
import com.jgraph.gaeawt.java.awt.geom.AffineTransform;
import com.jgraph.gaeawt.java.awt.geom.Arc2D;
import com.jgraph.gaeawt.java.awt.geom.Ellipse2D;
import com.jgraph.gaeawt.java.awt.geom.Line2D;
import com.jgraph.gaeawt.java.awt.geom.PathIterator;
import com.jgraph.gaeawt.java.awt.geom.RoundRectangle2D;
import com.jgraph.gaeawt.java.awt.image.AffineTransformOp;
import com.jgraph.gaeawt.java.awt.image.BufferedImage;
import com.jgraph.gaeawt.java.awt.image.BufferedImageOp;
import com.jgraph.gaeawt.java.awt.image.ImageObserver;
import com.jgraph.gaeawt.java.awt.image.Raster;
import com.jgraph.gaeawt.java.awt.image.WritableRaster;

/*
 * List of abstract methods to implement in subclusses
 * Graphics.copyArea(int x, int y, int width, int height, int dx, int dy)
 * Graphics.create()
 * Graphics2D.getDeviceConfiguration()
 * CommonGraphics2D.fillMultiRectAreaColor(MultiRectArea mra);
 * CommonGraphics2D.fillMultiRectAreaPaint(MultiRectArea mra);
 */

/**
 * CommonGraphics2D class is a super class for all system-dependent
 * implementations. It implements major part of Graphics and Graphics2D
 * abstract methods.
 * <h2>CommonGraphics2D Class Internals</h2>
 * <h3>Line and Shape Rasterizers</h3>
 * <p>
 * The CommonGraphics2D class splits all shapes into a set of rectangles 
 * to unify the drawing process for different operating systems and architectures. 
 * For this purpose Java 2D* uses the JavaShapeRasterizer and the JavaLineRasterizer 
 * classes from the org.apache.harmony.awt.gl.render package. The JavaShapeRasterizer 
 * class splits an object implementing a Shape interface into a set of rectangles and 
 * produces a MultiRectArea object. The JavaLineRasterizer class makes line drawing 
 * more accurate and processes lines with strokes, which are instances of the BasicStroke 
 * class.
 * </p>
 * <p>
 * To port the shape drawing to another platform you just need to override 
 * rectangle-drawing methods. However, if your operating system has functions to draw 
 * particular shapes, you can optimize your subclass of the CommonGraphics2D class by 
 * using this functionality in overridden methods.
 * </p>

 * <h3>Blitters</h3>
 * <p>
 * Blitter classes draw images on the display or buffered images. All blitters inherit 
 * the org.apache.harmony.awt.gl.render.Blitter interface.
 * </p>
 * <p>Blitters are divided into:
 * <ul>
 * <li>Native blitters for simple types of images, which the underlying native library 
 * can draw.</li> 
 * <li>Java* blitters for those types of images, which the underlying native library 
 * cannot handle.</li>
 * </ul></p>
 * <p>
 * DRL Java 2D* also uses blitters to fill the shapes and the user-defined subclasses 
 * of the java.awt.Paint class with paints, which the system does not support.
 * </p>
 *
 *<h3>Text Renderers</h3>
 *<p>
 *Text renderers draw strings and glyph vectors. All text renderers are subclasses 
 *of the org.apache.harmony.awt.gl.TextRenderer class.
 *</p>
 *
 */
public abstract class CommonGraphics2D extends Graphics2D
{

	private static final Map<RenderingHints.Key, Object> DEFAULT_RENDERING_HINTS;

	static final FontMetrics cacheFM[] = new FontMetrics[10];

	static
	{
		final Map<RenderingHints.Key, Object> m = new HashMap<RenderingHints.Key, Object>();

		m.put(RenderingHints.KEY_TEXT_ANTIALIASING,
				RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
		m.put(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_OFF);
		m.put(RenderingHints.KEY_STROKE_CONTROL,
				RenderingHints.VALUE_STROKE_DEFAULT);

		DEFAULT_RENDERING_HINTS = Collections.unmodifiableMap(m);
	}

	protected Surface dstSurf = null;

	protected Blitter blitter = NullBlitter.getInstance();

	protected RenderingHints hints = new RenderingHints(DEFAULT_RENDERING_HINTS);

	// Clipping things
	protected MultiRectArea clip = null;

	protected Paint paint = Color.WHITE;

	protected Color fgColor = Color.WHITE;

	protected Color bgColor = Color.BLACK;

	protected Composite composite = AlphaComposite.SrcOver;

	protected Stroke stroke = new BasicStroke();

	//TODO: Think more about FontRenderContext
	protected FontRenderContext frc = null;

	protected JavaShapeRasterizer jsr = new JavaShapeRasterizer();

	protected Font font = new Font("Dialog", Font.PLAIN, 12);; //$NON-NLS-1$

	protected TextRenderer jtr = FontManager.IS_FONTLIB ? FLTextRenderer
			.getInstance() : JavaTextRenderer.inst;

	// Current graphics transform
	protected AffineTransform transform = new AffineTransform();

	protected double[] matrix = new double[6];

	// Original user->device translation as transform and point
	//public AffineTransform origTransform = new AffineTransform();
	public Point origPoint = new Point(0, 0);

	// Print debug output or not
	protected static final boolean debugOutput = "1".equals(org.apache.harmony.awt.Utils.getSystemProperty("g2d.debug")); //$NON-NLS-1$ //$NON-NLS-2$

	// Constructors
	protected CommonGraphics2D()
	{
	}

	protected CommonGraphics2D(int tx, int ty)
	{
		this(tx, ty, null);
	}

	protected CommonGraphics2D(int tx, int ty, MultiRectArea clip)
	{
		setTransform(AffineTransform.getTranslateInstance(tx, ty));
		//origTransform = AffineTransform.getTranslateInstance(tx, ty);
		origPoint = new Point(tx, ty);
		setClip(clip);
	}

	// Public methods
	@Override
	public void addRenderingHints(Map<?, ?> hints)
	{
		this.hints.putAll(hints);
	}

	@Override
	public void clearRect(int x, int y, int width, int height)
	{
		Color c = getColor();
		Paint p = getPaint();
		setColor(getBackground());
		fillRect(x, y, width, height);
		setColor(c);
		setPaint(p);
		if (debugOutput)
		{
			System.err
					.println("CommonGraphics2D.clearRect(" + x + ", " + y + ", " + width + ", " + height + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
		}
	}

	@Override
	public void clipRect(int x, int y, int width, int height)
	{
		clip(new Rectangle(x, y, width, height));
	}

	@Override
	public void clip(Shape s)
	{
		if (s == null)
		{
			clip = null;
			return;
		}

		MultiRectArea mra = null;
		if (s instanceof MultiRectArea)
		{
			mra = new MultiRectArea((MultiRectArea) s);
			mra.translate((int) transform.getTranslateX(),
					(int) transform.getTranslateY());
		}
		else
		{
			int type = transform.getType();
			if (s instanceof Rectangle
					&& (type == AffineTransform.TYPE_IDENTITY || type == AffineTransform.TYPE_TRANSLATION))
			{
				mra = new MultiRectArea((Rectangle) s);
				if (type == AffineTransform.TYPE_TRANSLATION)
				{
					mra.translate((int) transform.getTranslateX(),
							(int) transform.getTranslateY());
				}
			}
			else
			{
				s = transform.createTransformedShape(s);
				mra = jsr.rasterize(s, 0.5);
			}
		}

		if (clip == null)
		{
			setTransformedClip(mra);
		}
		else
		{
			clip.intersect(mra);
			setTransformedClip(clip);
		}
	}

	@Override
	public void dispose()
	{
		// Do nothing for Java only classes
	}

	/***************************************************************************
	 *
	 *  Draw methods
	 *
	 ***************************************************************************/

	@Override
	public void draw(Shape s)
	{
		if (stroke instanceof BasicStroke
				&& ((BasicStroke) stroke).getLineWidth() <= 1)
		{
			//TODO: Think about drawing the shape in one fillMultiRectArea call
			BasicStroke bstroke = (BasicStroke) stroke;
			JavaLineRasterizer.LineDasher ld = (bstroke.getDashArray() == null) ? null
					: new JavaLineRasterizer.LineDasher(bstroke.getDashArray(),
							bstroke.getDashPhase());
			PathIterator pi = s.getPathIterator(transform, 0.5);
			float[] points = new float[6];
			int x1 = Integer.MIN_VALUE;
			int y1 = Integer.MIN_VALUE;
			int cx1 = Integer.MIN_VALUE;
			int cy1 = Integer.MIN_VALUE;
			while (!pi.isDone())
			{
				switch (pi.currentSegment(points))
				{
					case PathIterator.SEG_MOVETO:
						x1 = (int) Math.floor(points[0]);
						y1 = (int) Math.floor(points[1]);
						cx1 = x1;
						cy1 = y1;
						break;
					case PathIterator.SEG_LINETO:
						int x2 = (int) Math.floor(points[0]);
						int y2 = (int) Math.floor(points[1]);
						fillMultiRectArea(JavaLineRasterizer.rasterize(x1, y1,
								x2, y2, null, ld, false));
						x1 = x2;
						y1 = y2;
						break;
					case PathIterator.SEG_CLOSE:
						x2 = cx1;
						y2 = cy1;
						fillMultiRectArea(JavaLineRasterizer.rasterize(x1, y1,
								x2, y2, null, ld, false));
						x1 = x2;
						y1 = y2;
						break;
				}
				pi.next();
			}
		}
		else
		{
			s = stroke.createStrokedShape(s);
			s = transform.createTransformedShape(s);
			fillMultiRectArea(jsr.rasterize(s, 0.5));
		}
	}

	@Override
	public void drawArc(int x, int y, int width, int height, int sa, int ea)
	{
		if (stroke instanceof BasicStroke
				&& ((BasicStroke) stroke).getLineWidth() <= 1
				&& ((BasicStroke) stroke).getDashArray() == null
				&& (transform.isIdentity() || transform.getType() == AffineTransform.TYPE_TRANSLATION))
		{
			Point p = new Point(x, y);
			transform.transform(p, p);
			MultiRectArea mra = JavaArcRasterizer.rasterize(x, y, width,
					height, sa, ea, clip);
			fillMultiRectArea(mra);
			return;
		}
		draw(new Arc2D.Float(x, y, width, height, sa, ea, Arc2D.OPEN));
	}

	@Override
	public boolean drawImage(Image image, int x, int y, Color bgcolor,
			ImageObserver imageObserver)
	{

		if (image == null)
		{
			return true;
		}

		boolean done = false;
		boolean somebits = false;
		Surface srcSurf = null;
		if (image instanceof OffscreenImage)
		{
			OffscreenImage oi = (OffscreenImage) image;
			if ((oi.getState() & ImageObserver.ERROR) != 0)
			{
				return false;
			}
			done = oi.prepareImage(imageObserver);
			somebits = (oi.getState() & ImageObserver.SOMEBITS) != 0;
			srcSurf = oi.getImageSurface();
		}
		else // BufferedImage only
		{
			done = true;
			srcSurf = ((BufferedImage)image).getImageSurface();
		}

		if (done || somebits)
		{
			int w = srcSurf.getWidth();
			int h = srcSurf.getHeight();
			blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h,
					(AffineTransform) transform.clone(), composite, bgcolor,
					clip);
		}
		return done;
	}

	@Override
	public boolean drawImage(Image image, int x, int y,
			ImageObserver imageObserver)
	{
		return drawImage(image, x, y, null, imageObserver);
	}

	@Override
	public boolean drawImage(Image image, int x, int y, int width, int height,
			Color bgcolor, ImageObserver imageObserver)
	{

		if (image == null)
		{
			return true;
		}
		if (width == 0 || height == 0)
		{
			return true;
		}

		boolean done = false;
		boolean somebits = false;
		Surface srcSurf = null;

		if (image instanceof OffscreenImage)
		{
			OffscreenImage oi = (OffscreenImage) image;
			if ((oi.getState() & ImageObserver.ERROR) != 0)
			{
				return false;
			}
			done = oi.prepareImage(imageObserver);
			somebits = (oi.getState() & ImageObserver.SOMEBITS) != 0;
			srcSurf = oi.getImageSurface();
		}
		else // BufferedImage only
		{
			done = true;
			srcSurf = ((BufferedImage)image).getImageSurface();
		}

		if (done || somebits)
		{
			int w = srcSurf.getWidth();
			int h = srcSurf.getHeight();
			if (w == width && h == height)
			{
				blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h,
						(AffineTransform) transform.clone(), composite,
						bgcolor, clip);
			}
			else
			{
				AffineTransform xform = new AffineTransform();
				xform.setToScale((float) width / w, (float) height / h);
				blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h,
						(AffineTransform) transform.clone(), xform, composite,
						bgcolor, clip);
			}
		}
		return done;
	}

	@Override
	public boolean drawImage(Image image, int x, int y, int width, int height,
			ImageObserver imageObserver)
	{
		return drawImage(image, x, y, width, height, null, imageObserver);
	}

	@Override
	public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2,
			int sx1, int sy1, int sx2, int sy2, Color bgcolor,
			ImageObserver imageObserver)
	{

		if (image == null)
		{
			return true;
		}
		if (dx1 == dx2 || dy1 == dy2 || sx1 == sx2 || sy1 == sy2)
		{
			return true;
		}

		boolean done = false;
		boolean somebits = false;
		Surface srcSurf = null;
		if (image instanceof OffscreenImage)
		{
			OffscreenImage oi = (OffscreenImage) image;
			if ((oi.getState() & ImageObserver.ERROR) != 0)
			{
				return false;
			}
			done = oi.prepareImage(imageObserver);
			somebits = (oi.getState() & ImageObserver.SOMEBITS) != 0;
			srcSurf = oi.getImageSurface();
		}
		else // BufferedImage only
		{
			done = true;
			srcSurf = ((BufferedImage)image).getImageSurface();
		}

		if (done || somebits)
		{

			int dstX = dx1;
			int dstY = dy1;
			int srcX = sx1;
			int srcY = sy1;

			int dstW = dx2 - dx1;
			int dstH = dy2 - dy1;
			int srcW = sx2 - sx1;
			int srcH = sy2 - sy1;

			if (srcW == dstW && srcH == dstH)
			{
				blitter.blit(srcX, srcY, srcSurf, dstX, dstY, dstSurf, srcW,
						srcH, (AffineTransform) transform.clone(), composite,
						bgcolor, clip);
			}
			else
			{
				AffineTransform xform = new AffineTransform();
				xform.setToScale((float) dstW / srcW, (float) dstH / srcH);
				blitter.blit(srcX, srcY, srcSurf, dstX, dstY, dstSurf, srcW,
						srcH, (AffineTransform) transform.clone(), xform,
						composite, bgcolor, clip);
			}
		}
		return done;
	}

	@Override
	public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2,
			int sx1, int sy1, int sx2, int sy2, ImageObserver imageObserver)
	{

		return drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null,
				imageObserver);
	}

	@Override
	public void drawImage(BufferedImage bufImage, BufferedImageOp op, int x,
			int y)
	{

		if (bufImage == null)
		{
			return;
		}

		if (op == null)
		{
			drawImage(bufImage, x, y, null);
		}
		else if (op instanceof AffineTransformOp)
		{
			AffineTransformOp atop = (AffineTransformOp) op;
			AffineTransform xform = atop.getTransform();
			Surface srcSurf = bufImage.getImageSurface();
			int w = srcSurf.getWidth();
			int h = srcSurf.getHeight();
			blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h,
					(AffineTransform) transform.clone(), xform, composite,
					null, clip);
		}
		else
		{
			bufImage = op.filter(bufImage, null);
			Surface srcSurf = bufImage.getImageSurface();
			int w = srcSurf.getWidth();
			int h = srcSurf.getHeight();
			blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h,
					(AffineTransform) transform.clone(), composite, null, clip);
		}
	}

	@Override
	public boolean drawImage(Image image, AffineTransform trans,
			ImageObserver imageObserver)
	{

		if (image == null)
		{
			return true;
		}
		if (trans == null || trans.isIdentity())
		{
			return drawImage(image, 0, 0, imageObserver);
		}

		boolean done = false;
		boolean somebits = false;
		Surface srcSurf = null;
		if (image instanceof OffscreenImage)
		{
			OffscreenImage oi = (OffscreenImage) image;
			if ((oi.getState() & ImageObserver.ERROR) != 0)
			{
				return false;
			}
			done = oi.prepareImage(imageObserver);
			somebits = (oi.getState() & ImageObserver.SOMEBITS) != 0;
			srcSurf = oi.getImageSurface();
		}
		else // BufferedImage only
		{
			done = true;
			srcSurf = ((BufferedImage)image).getImageSurface();
		}

		if (done || somebits)
		{
			int w = srcSurf.getWidth();
			int h = srcSurf.getHeight();
			AffineTransform xform = (AffineTransform) transform.clone();
			xform.concatenate(trans);
			blitter.blit(0, 0, srcSurf, 0, 0, dstSurf, w, h, xform, composite,
					null, clip);
		}
		return done;
	}

	@Override
	public void drawLine(int x1, int y1, int x2, int y2)
	{
		if (debugOutput)
		{
			System.err
					.println("CommonGraphics2D.drawLine(" + x1 + ", " + y1 + ", " + x2 + ", " + y2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
		}

		if (stroke instanceof BasicStroke
				&& ((BasicStroke) stroke).getLineWidth() <= 1)
		{
			BasicStroke bstroke = (BasicStroke) stroke;
			Point p1 = new Point(x1, y1);
			Point p2 = new Point(x2, y2);
			transform.transform(p1, p1);
			transform.transform(p2, p2);
			JavaLineRasterizer.LineDasher ld = (bstroke.getDashArray() == null) ? null
					: new JavaLineRasterizer.LineDasher(bstroke.getDashArray(),
							bstroke.getDashPhase());
			MultiRectArea mra = JavaLineRasterizer.rasterize(p1.x, p1.y, p2.x,
					p2.y, null, ld, false);
			fillMultiRectArea(mra);
			return;
		}
		draw(new Line2D.Float(x1, y1, x2, y2));
	}

	@Override
	public void drawOval(int x, int y, int width, int height)
	{
		if (stroke instanceof BasicStroke
				&& ((BasicStroke) stroke).getLineWidth() <= 1
				&& ((BasicStroke) stroke).getDashArray() == null
				&& (transform.isIdentity() || transform.getType() == AffineTransform.TYPE_TRANSLATION))
		{
			Point p = new Point(x, y);
			transform.transform(p, p);
			MultiRectArea mra = JavaArcRasterizer.rasterize(p.x, p.y, width,
					height, 0, 360, clip);
			fillMultiRectArea(mra);
			return;
		}
		draw(new Ellipse2D.Float(x, y, width, height));
	}

	@Override
	public void drawPolygon(int[] xpoints, int[] ypoints, int npoints)
	{
		draw(new Polygon(xpoints, ypoints, npoints));
	}

	@Override
	public void drawPolygon(Polygon polygon)
	{
		draw(polygon);
	}

	@Override
	public void drawPolyline(int[] xpoints, int[] ypoints, int npoints)
	{
		for (int i = 0; i < npoints - 1; i++)
		{
			drawLine(xpoints[i], ypoints[i], xpoints[i + 1], ypoints[i + 1]);
		}
	}

	@Override
	public void drawRoundRect(int x, int y, int width, int height,
			int arcWidth, int arcHeight)
	{
		if (debugOutput)
		{
			System.err
					.println("CommonGraphics2D.drawRoundRect(" + x + ", " + y + ", " + width + ", " + height + "," + arcWidth + ", " + arcHeight + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
		}

		draw(new RoundRectangle2D.Float(x, y, width, height, arcWidth,
				arcHeight));
	}

	/***************************************************************************
	 *
	 *  String methods
	 *
	 ***************************************************************************/

	@Override
	public void drawString(AttributedCharacterIterator iterator, float x,
			float y)
	{
		GlyphVector gv = font.createGlyphVector(frc, iterator);
		drawGlyphVector(gv, x, y);
	}

	@Override
	public void drawString(AttributedCharacterIterator iterator, int x, int y)
	{
		drawString(iterator, (float) x, (float) y);
	}

	@Override
	public void drawString(String str, int x, int y)
	{
		drawString(str, (float) x, (float) y);
	}

	/***************************************************************************
	 *
	 *  Fill methods
	 *
	 ***************************************************************************/

	@Override
	public void fill(Shape s)
	{
		s = transform.createTransformedShape(s);
		MultiRectArea mra = jsr.rasterize(s, 0.5);
		fillMultiRectArea(mra);
	}

	@Override
	public void fillArc(int x, int y, int width, int height, int sa, int ea)
	{
		fill(new Arc2D.Float(x, y, width, height, sa, ea, Arc2D.PIE));
	}

	@Override
	public void fillOval(int x, int y, int width, int height)
	{
		fill(new Ellipse2D.Float(x, y, width, height));
	}

	@Override
	public void fillPolygon(int[] xpoints, int[] ypoints, int npoints)
	{
		fill(new Polygon(xpoints, ypoints, npoints));
	}

	@Override
	public void fillPolygon(Polygon polygon)
	{
		fill(polygon);
	}

	@Override
	public void fillRect(int x, int y, int width, int height)
	{
		if (debugOutput)
		{
			System.err
					.println("CommonGraphics2D.fillRect(" + x + ", " + y + ", " + width + ", " + height + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
		}

		fill(new Rectangle(x, y, width, height));
	}

	@Override
	public void fillRoundRect(int x, int y, int width, int height,
			int arcWidth, int arcHeight)
	{
		if (debugOutput)
		{
			System.err
					.println("CommonGraphics2D.fillRoundRect(" + x + ", " + y + ", " + width + ", " + height + "," + arcWidth + ", " + arcHeight + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
		}

		fill(new RoundRectangle2D.Float(x, y, width, height, arcWidth,
				arcHeight));
	}

	/***************************************************************************
	 *
	 *  Get methods
	 *
	 ***************************************************************************/

	@Override
	public Color getBackground()
	{
		return bgColor;
	}

	@Override
	public Shape getClip()
	{
		if (clip == null)
		{
			return null;
		}

		MultiRectArea res = new MultiRectArea(clip);
		res.translate(-Math.round((float) transform.getTranslateX()),
				-Math.round((float) transform.getTranslateY()));
		return res;
	}

	@Override
	public Rectangle getClipBounds()
	{
		if (clip == null)
		{
			return null;
		}

		Rectangle res = (Rectangle) clip.getBounds().clone();
		res.translate(-Math.round((float) transform.getTranslateX()),
				-Math.round((float) transform.getTranslateY()));
		return res;
	}

	@Override
	public Color getColor()
	{
		return fgColor;
	}

	@Override
	public Composite getComposite()
	{
		return composite;
	}

	@Override
	public Font getFont()
	{
		return font;
	}

	@Override
	public FontRenderContext getFontRenderContext()
	{
		AffineTransform at;
		if (frc == null)
		{
			GraphicsConfiguration gc = getDeviceConfiguration();
			if (gc != null)
			{
				at = gc.getDefaultTransform();
				at.concatenate(gc.getNormalizingTransform());
			}
			else
				at = null;

			boolean isAa = (hints.get(RenderingHints.KEY_TEXT_ANTIALIASING) == RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
			boolean isFm = (hints.get(RenderingHints.KEY_FRACTIONALMETRICS) == RenderingHints.VALUE_FRACTIONALMETRICS_ON);
			frc = new FontRenderContext(at, isAa, isFm);
		}
		return frc;
	}

	@Override
	public Paint getPaint()
	{
		return paint;
	}

	@Override
	public Object getRenderingHint(RenderingHints.Key key)
	{
		return hints.get(key);
	}

	@Override
	public RenderingHints getRenderingHints()
	{
		return hints;
	}

	@Override
	public Stroke getStroke()
	{
		return stroke;
	}

	@Override
	public AffineTransform getTransform()
	{
		return (AffineTransform) transform.clone();
	}

	@Override
	public boolean hit(Rectangle rect, Shape s, boolean onStroke)
	{
		//TODO: Implement method....
		return false;
	}

	/***************************************************************************
	 *
	 *  Transformation methods
	 *
	 ***************************************************************************/

	@Override
	public void rotate(double theta)
	{
		transform.rotate(theta);
		transform.getMatrix(matrix);
	}

	@Override
	public void rotate(double theta, double x, double y)
	{
		transform.rotate(theta, x, y);
		transform.getMatrix(matrix);
	}

	@Override
	public void scale(double sx, double sy)
	{
		transform.scale(sx, sy);
		transform.getMatrix(matrix);
	}

	@Override
	public void shear(double shx, double shy)
	{
		transform.shear(shx, shy);
		transform.getMatrix(matrix);
	}

	@Override
	public void transform(AffineTransform at)
	{
		transform.concatenate(at);
		transform.getMatrix(matrix);
	}

	@Override
	public void translate(double tx, double ty)
	{
		if (debugOutput)
		{
			System.err
					.println("CommonGraphics2D.translate(" + tx + ", " + ty + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}

		transform.translate(tx, ty);
		transform.getMatrix(matrix);
	}

	@Override
	public void translate(int tx, int ty)
	{
		if (debugOutput)
		{
			System.err
					.println("CommonGraphics2D.translate(" + tx + ", " + ty + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}

		transform.translate(tx, ty);
		transform.getMatrix(matrix);
	}

	/***************************************************************************
	 *
	 *  Set methods
	 *
	 ***************************************************************************/

	@Override
	public void setBackground(Color color)
	{
		bgColor = color;
	}

	@Override
	public void setClip(int x, int y, int width, int height)
	{
		setClip(new Rectangle(x, y, width, height));
	}

	@Override
	public void setClip(Shape s)
	{
		if (s == null)
		{
			setTransformedClip(null);
			if (debugOutput)
			{
				System.err.println("CommonGraphics2D.setClip(null)"); //$NON-NLS-1$
			}
			return;
		}

		if (debugOutput)
		{
			System.err
					.println("CommonGraphics2D.setClip(" + s.getBounds() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
		}

		if (s instanceof MultiRectArea)
		{
			MultiRectArea nclip = new MultiRectArea((MultiRectArea) s);
			nclip.translate(Math.round((float) transform.getTranslateX()),
					Math.round((float) transform.getTranslateY()));
			setTransformedClip(nclip);
		}
		else
		{
			int type = transform.getType();
			if (s instanceof Rectangle
					&& (type == AffineTransform.TYPE_IDENTITY || type == AffineTransform.TYPE_TRANSLATION))
			{
				MultiRectArea nclip = new MultiRectArea((Rectangle) s);
				if (type == AffineTransform.TYPE_TRANSLATION)
				{
					nclip.translate((int) transform.getTranslateX(),
							(int) transform.getTranslateY());
				}
				setTransformedClip(nclip);
			}
			else
			{
				s = transform.createTransformedShape(s);
				setTransformedClip(jsr.rasterize(s, 0.5));
			}
		}
	}

	@Override
	public void setColor(Color color)
	{
		if (color != null)
		{
			fgColor = color;
			paint = color;
		}
	}

	@Override
	public void setComposite(Composite composite)
	{
		this.composite = composite;
	}

	@Override
	public void setFont(Font font)
	{
		this.font = font;
	}

	@Override
	public void setPaint(Paint paint)
	{
		if (paint == null)
			return;

		this.paint = paint;
		if (paint instanceof Color)
		{
			fgColor = (Color) paint;
		}
	}

	@Override
	public void setPaintMode()
	{
		composite = AlphaComposite.SrcOver;
	}

	@Override
	public void setRenderingHint(RenderingHints.Key key, Object value)
	{
		hints.put(key, value);
	}

	@Override
	public void setRenderingHints(Map<?, ?> hints)
	{
		this.hints.clear();
		this.hints.putAll(DEFAULT_RENDERING_HINTS);
		this.hints.putAll(hints);
	}

	@Override
	public void setStroke(Stroke stroke)
	{
		this.stroke = stroke;
	}

	@Override
	public void setTransform(AffineTransform transform)
	{
		this.transform = transform;

		transform.getMatrix(matrix);
	}

	@Override
	public void setXORMode(Color color)
	{
		composite = new XORComposite(color);
	}

	// Protected methods
	protected void setTransformedClip(MultiRectArea clip)
	{
		this.clip = clip;
	}

	/**
	 * This method fills the given MultiRectArea with current paint.
	 * It calls fillMultiRectAreaColor and fillMultiRectAreaPaint 
	 * methods depending on the type of current paint.
	 * @param mra MultiRectArea to fill
	 */
	protected void fillMultiRectArea(MultiRectArea mra)
	{
		if (clip != null)
		{
			mra.intersect(clip);
		}

		// Return if all stuff is clipped
		if (mra.rect[0] < 5)
		{
			return;
		}

		if (debugOutput)
		{
			System.err
					.println("CommonGraphics2D.fillMultiRectArea(" + mra + ")"); //$NON-NLS-1$ //$NON-NLS-2$
		}

		if (paint instanceof Color)
		{
			fillMultiRectAreaColor(mra);
		}
		else
		{
			fillMultiRectAreaPaint(mra);
		}
	}

	/**
	 * This method fills the given MultiRectArea with solid color.
	 * @param mra MultiRectArea to fill
	 */
	protected void fillMultiRectAreaColor(MultiRectArea mra)
	{
		fillMultiRectAreaPaint(mra);
	}

	/**
	 * This method fills the given MultiRectArea with any paint.
	 * @param mra MultiRectArea to fill
	 */
	protected void fillMultiRectAreaPaint(MultiRectArea mra)
	{
		Rectangle rec = mra.getBounds();
		int x = rec.x;
		int y = rec.y;
		int w = rec.width;
		int h = rec.height;
		if (w <= 0 || h <= 0)
		{
			return;
		}
		PaintContext pc = paint.createContext(null, rec, rec, transform, hints);
		Raster r = pc.getRaster(x, y, w, h);
		WritableRaster wr;
		if (r instanceof WritableRaster)
		{
			wr = (WritableRaster) r;
		}
		else
		{
			wr = r.createCompatibleWritableRaster();
			wr.setRect(r);
		}
		Surface srcSurf = new ImageSurface(pc.getColorModel(), wr);
		blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h, composite, null, mra);
		srcSurf.dispose();
	}

	/**
	 * Copies graphics class fields. 
	 * Used in create method
	 * 
	 * @param copy Graphics class to copy
	 */
	protected void copyInternalFields(CommonGraphics2D copy)
	{
		if (clip == null)
		{
			copy.setTransformedClip(null);
		}
		else
		{
			copy.setTransformedClip(new MultiRectArea(clip));
		}
		copy.setBackground(bgColor);
		copy.setColor(fgColor);
		copy.setPaint(paint);
		copy.setComposite(composite);
		copy.setStroke(stroke);
		copy.setFont(font);
		copy.setTransform(new AffineTransform(transform));
		//copy.origTransform = new AffineTransform(origTransform);
		copy.origPoint = new Point(origPoint);
	}

	public void flush()
	{
	}

	@SuppressWarnings("deprecation")
	@Override
	public FontMetrics getFontMetrics(Font font)
	{
		FontMetrics fm;
		for (FontMetrics element : cacheFM)
		{
			fm = element;
			if (fm == null)
			{
				break;
			}

			if (fm.getFont().equals(font))
			{
				return fm;
			}
		}
		fm = new FontMetricsImpl(font);

		System.arraycopy(cacheFM, 0, cacheFM, 1, cacheFM.length - 1);
		cacheFM[0] = fm;

		return fm;
	}
}
